/*
 * DigaltalLogicRelay.cpp
 *
 *  Created on: 2012. 10. 16.
 *      Author: dalxx
 */
#include <Arduino.h>
#include "../StringStream.h"
#include "RelayContainer.h"
#include "ParseTool.h"
#include "RelayDL.h"
#undef SERIAL_TRACE_ON

namespace PLCLib {

BooleanOperation RelayDL::operationBuffer[MAX_OPERATIONSIZE];
int RelayDL::nextBuffer = 0;

RelayDL::~RelayDL() {
	this->removeAll();
}

RelayDL::RelayDL() {
	this->numRelay = 0;
	this->relayList = NULL;
	this->container = NULL;
	this->DLList = NULL;
}

void RelayDL::init(int num_relay, Relay* relay_list,  RelayContainer* relay_container) {
	this->numRelay = num_relay;
	this->relayList = relay_list;
	this->container = relay_container;
	this->DLList = (DL*) malloc(
			this->numRelay * sizeof(DL));
#ifdef  SERIAL_TRACE_ON
	Serial.print(" numDL/TM: ");
	Serial.print(numRelay);
	Serial.print(" and: ");
	Serial.print((int)RelayDL::_and);
	Serial.print(" or: ");
	Serial.print((int)RelayDL::_or);
	Serial.print(" not: ");
	Serial.print((int)RelayDL::_not);
	Serial.println();
#endif
}

void RelayDL::removeAll() {
	free(this->DLList);
	RelayDL::nextBuffer = 0;
	this->numRelay = 0;
	this->relayList = NULL;
	this->container = NULL;
	this->DLList = NULL;
}

void RelayDL::calculateNext() {

	int i;
	for (i = 0; i < this->numRelay; i++) {
		this->DLList[i].nextStatus = *this->DLList[i].val;
	}
}
void RelayDL::commit() {
	int i;
	for (i = 0; i < this->numRelay; i++) {
		this->relayList[i].status=this->DLList[i].nextStatus;
	}
}

void RelayDL::setRelay(int index, char* logic, int size) {

	StringStream stream(logic, size);
	ParseTool::eatWhite(stream);

	this->DLList[index].val=this->buildOperation(stream);
#ifdef  SERIAL_TRACE_ON
	Serial.print(" index: ");
	Serial.print(index);

	Serial.print(" logic: ");
	Serial.print(logic);
	Serial.print(" size: ");
	Serial.print(size);
	Serial.print(" val: ");
	Serial.print((int)this->DLList[index].val);
	Serial.print(" nextBuffer: ");
	Serial.print(this->nextBuffer);
	Serial.println();
	for (int i = 0; i <nextBuffer ; i++) {
		BooleanOperation* o=&operationBuffer[i];
		Serial.print(" op: ");
		Serial.print((int)o->operation);
		Serial.print(" p1: ");
		Serial.print((int)o->param1);
		Serial.print(" p2: ");
		Serial.print((int)o->param2);
		Serial.print(" val: ");
				Serial.print((int)&o->val);
		Serial.println();
	}
#endif


}

BooleanOperation* RelayDL::getNextOperationBuffer() {
	if(RelayDL::nextBuffer<MAX_OPERATIONSIZE)
	{
		BooleanOperation* ret=RelayDL::operationBuffer+RelayDL::nextBuffer;
		RelayDL::nextBuffer++;
		return ret;		
	}
	return NULL;	
}

void RelayDL::calcuateOperation() {
	int i;
	for (i = 0; i <nextBuffer ; i++) {
		BooleanOperation* o=&operationBuffer[i];
		o->val=(o->operation)(o->param1, o->param2);
//#ifdef  SERIAL_TRACE_ON
//		static bool a=false;
//		if(a!=o->val)
//		{
//	Serial.print("calcuatieOperation index: ");
//	Serial.print(i);
//	Serial.print(" p1: ");
//	Serial.print((int)*o->param1);
//	Serial.print(" p2: ");
//	Serial.print((int)*o->param2);
//	Serial.print(" val: ");
//		Serial.print((int)o->val);
//	Serial.println();
//	a=o->val;
//		}
//#endif
	}
}

//return : parsed relay operation 
bool* RelayDL::pasrseOneTerm(StringStream& stream) {
	int pos;
	bool* p1;

	ParseTool::eatWhite(stream);
	pos=stream.getPosition();
	int ch = stream.read();
#ifdef  SERIAL_TRACE_ON
	Serial.print(" parseOneTerm ch: ");
	Serial.print((char)ch);
	Serial.print(" pos: ");
	Serial.print(pos);
	Serial.println();
#endif
	switch(ch)
	{
	case -1:
		return NULL;
		break;
	case '!':
		{
			p1=this->pasrseOneTerm(stream);
			BooleanOperation* op=this->getNextOperationBuffer();
			op->operation =  RelayDL::_not;
			op->param1 = p1;
			op->param2 = NULL;
			return &op->val;
		}
		break;
	case '(':
		{
			this->eatParenthesis(stream);
			int size=stream.getPosition()-pos-1;
			StringStream parenthesisStream(stream.getBuffer()+pos+1, size);
#ifdef  SERIAL_TRACE_ON
	Serial.print(" Parenthesis string: ");
	Serial.print(stream.getBuffer()+pos);
	Serial.print(" size: ");
	Serial.print(size);
	Serial.println();
#endif
			return this->buildOperation(parenthesisStream);
			break;
		}
	default: //relay number
		{
			//stream.peekNextDigit();
			stream.seek(pos);
			int relnum=stream.parseInt();
//#ifdef  SERIAL_TRACE_ON
//	Serial.print(" relay number: ");
//	Serial.print(relnum);
//	//Serial.print(" &status: ");
//	//Serial.print((int) &rel->status);
//	Serial.println();
//#endif
			Relay* rel=this->container->findRelay(relnum) ;
#ifdef  SERIAL_TRACE_ON
	Serial.print(" relay number: ");
	Serial.print(relnum);
	Serial.print(" &status: ");
	Serial.print((int) &rel->status);
	Serial.println();
#endif
			return &rel->status;
			break;
		}
	}

	return NULL;
}

bool* RelayDL::buildOperation(StringStream& stream) {
	int pos;
	bool* p1,*p2;
	BooleanOperation* op;
	int ch;
	ParseTool::eatWhite(stream);
	p1=this->pasrseOneTerm(stream);
	do{
		ParseTool::eatWhite(stream);
		pos=stream.getPosition();
		ch = stream.read();
#ifdef  SERIAL_TRACE_ON
	Serial.print(" build operation ch: ");
	Serial.print((char)ch);
	Serial.print(" pos: ");
	Serial.print(pos);
	Serial.println();
#endif
		switch (ch) {
		case -1:
			break;
		case '&':
			p2=this->pasrseOneTerm(stream);
			op=this->getNextOperationBuffer();
			op->operation =  RelayDL::_and;
			op->param1 = p1;
			op->param2 = p2;
			p1=&op->val;
			break;
		case '|':
			p2=this->pasrseOneTerm(stream);
			op=this->getNextOperationBuffer();
			op->operation =  RelayDL::_or;
			op->param1 = p1;
			op->param2 = p2;
			p1=&op->val;
			break;
		default: //not expected, illregal

			break;
		}
	}while(ch!=-1);
	return p1;

}



int RelayDL::eatParenthesis(Stream& stream) {
	int depth=1;
	for(int ch=stream.read();ch!=-1;ch=stream.read())
	{
		if(ch=='(')	depth++;
		else if(ch==')')
		{
			depth--;
			if(depth==0) return 0;
		}
	}
	return -1;
	
}



bool RelayDL::_and(bool* p1, bool* p2) {
	return *p1 && *p2;
}

bool RelayDL::_or(bool* p1, bool* p2) {
	return *p1 || *p2;
}

bool RelayDL::_not(bool* p1, bool* p2) {
	return !*p1 ;
}



bool RelayDL::_ref(bool* p1, bool* p2) {
	return *p1;
}

} /* namespace PLCLib */
